package org.zoomdev.zoom.dao.entity;

import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.Record;
import org.zoomdev.zoom.dao.SplitTableRule;
import org.zoomdev.zoom.dao.configs.SplitTableConfig;
import org.zoomdev.zoom.dao.meta.LazyDatabaseMeta;
import org.zoomdev.zoom.dao.meta.TableKeyMeta;
import org.zoomdev.zoom.dao.naming.NameMappingFactory;
import org.zoomdev.zoom.dao.naming.impl.DetectPrefixNameMappingFactory;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class EntityFactory {
    private final LazyDatabaseMeta databaseMeta;

    private final ConcurrentHashMap<EntityKey, Entity> map = new ConcurrentHashMap<>(256);
    private final ConcurrentHashMap<String, SplitTableConfig> rules = new ConcurrentHashMap<>();
    private final Dao dao;
    private NameMappingFactory nameMappingFactory = DetectPrefixNameMappingFactory.DEFAULT;

    public EntityFactory(Dao dao) {
        this.dao = dao;
        this.databaseMeta = new LazyDatabaseMeta();
    }

    public void registerSplitRule(String column, SplitTableRule rule) {
        String[] parts = column.split("\\.");
        if (parts.length == 1) {
            throw new ZoomException("column的形式为: table.column");
        }
        rules.put(parts[0].toUpperCase(), new SplitTableConfig(parts[1], rule));
    }

    public <T> Entity<T> getEntity(final Class<T> type) {
        return map.computeIfAbsent(new EntityKey(type), (EntityKey key) -> buildEntity(type, null));
    }

    public EntityBuilder builder() {
        return new EntityBuilder(this);
    }

//    public Entity getEntity(final String... tables) {
//        return map.computeIfAbsent(new EntityKey(null, tables), (EntityKey key) ->
//                recordEntityFactory.getEntity(null, TableSource.fromArray(key.tables)));
//    }

    public Entity getEntity(final Class<?> type, final String table) {
        return map.computeIfAbsent(new EntityKey(type, table), (EntityKey key) -> buildEntity(type, table));
    }

    @SuppressWarnings("unchecked")
    public Entity<?> build(EntityBuilder builder) {
        Entity entity = new Entity<>();
        String table = builder.table.getTable();
        TableKeyMeta tableMeta = getTableMeta(table);
        Field2ColumnMappingConfig[] list = Field2ColumnMappingConfig.fromSingle(nameMappingFactory, builder.table, tableMeta);
        entity.setColumnConfigs(list);
        if (builder.type != null) {
            entity.setType(builder.type);
        } else {
            entity.setType(Record.class);
        }
        entity.setTable(builder.table);
        entity.setTableMeta(tableMeta);

        buildSplitRules(entity, table, list);

        return entity;
    }

    public TableKeyMeta getTableMeta(String table) {
        return databaseMeta.get(dao.getDataSource(), dao.getDriver(), table);
    }

    public void clearCache() {
        map.clear();
    }

    public void setNameMappingFactory(NameMappingFactory nameMappingFactory) {
        this.nameMappingFactory = nameMappingFactory;
    }

    private Entity buildEntity(Class<?> type, String table) {
        if (type == null && table == null) {
            throw new DaoException("type和table不能都空");
        }
        if (table == null && Map.class.isAssignableFrom(type)) {
            throw new DaoException("不支持Record直接绑定Entity,需要指定至少一个表名称");
        }
        EntityBuilder builder = builder();
        if (type != null) {
            builder.type(type);
        }
        if (table != null) {
            builder.table(table);
        }
        return builder.build();
    }

    private void buildSplitRules(Entity entity, String table, Field2ColumnMappingConfig[] list) {
        SplitTableConfig config = rules.get(table.toUpperCase());
        if (config != null) {
            Field2ColumnMappingConfig splitColumn = Field2ColumnMappingConfig.find(list, config.getColumn());
            if (splitColumn == null) {
                throw new DaoException("找不到分表配置: table:" + table + " column:" + config.getColumn());
            }
            entity.setSplitColumn(splitColumn);
            entity.setSplitTableRule(config.getRule());
        }
    }

    private static class EntityKey {

        private final int h;
        String[] tables;
        Class<?> type;

        public EntityKey(Class<?> type, String... tables) {
            this.type = type;
            this.tables = tables;
            int h = 0;
            if (type != null) {
                h = 31 + type.hashCode();
            }
            for (String table : tables) {
                h = 31 * h + table.hashCode();
            }
            this.h = h;
        }

        @Override
        public int hashCode() {
            return h;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof EntityKey) {
                EntityKey key = (EntityKey) obj;
                if (key.type != this.type) {
                    return false;
                }

                return Arrays.equals(key.tables, this.tables);

            }
            return false;
        }
    }
}
